import os
import uuid
import json
import aiofiles
import aiohttp
import hashlib
import secrets
from datetime import datetime, date
from typing import List, Optional, Dict, Any, Tuple
from pathlib import Path
from fastapi import UploadFile, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession

from module_speech.dao.speech_dao import SpeechRecordDao
from module_speech.dao.speech_client_dao import ClientUserDao
from module_speech.dao.speech_usage_dao import ClientUsageDao
from module_speech.entity.do.speech_record_do import SpeechRecordDO
from module_speech.entity.do.speech_client_user_do import SpeechClientUserDO
from module_speech.entity.vo.speech_vo import (
    SpeechRecognizeRequest, SpeechRecognizeResponse, 
    SpeechQueryModel, SpeechDeleteModel, RealtimeSpeechMessage
)
from module_speech.service.funasr_engine import funasr_engine
from utils.log_util import logger
from utils.common_util import CamelCaseUtil
from exceptions.exception import ServiceException


class SpeechService:
    """
    语音识别服务层
    """
    
    # 支持的音频格式
    SUPPORTED_AUDIO_FORMATS = ['.wav', '.mp3', '.flac', '.aac', '.m4a', '.ogg']
    
    # 文件上传配置
    MAX_FILE_SIZE = 100 * 1024 * 1024  # 100MB
    UPLOAD_DIR = Path("uploads/speech")
    
    @classmethod
    async def init_upload_dir(cls):
        """初始化上传目录"""
        cls.UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
    
    @classmethod
    async def upload_and_recognize_audio(cls, 
                                       db: AsyncSession,
                                       file: UploadFile,
                                       request_params: SpeechRecognizeRequest,
                                       user_id: int = None,
                                       client_user: SpeechClientUserDO = None) -> SpeechRecognizeResponse:
        """
        上传音频文件并进行识别
        
        :param db: 数据库会话
        :param file: 上传的音频文件
        :param request_params: 识别参数
        :param user_id: 系统用户ID（可选）
        :param client_user: 客户端用户对象（可选）
        :return: 识别结果
        """
        try:
            # 初始化上传目录
            await cls.init_upload_dir()
            
            # 验证文件
            await cls._validate_audio_file(file)
            
            # 确定用户类型和ID
            if client_user:
                user_type = 2  # 客户端用户
                current_user_id = client_user.client_user_id
                
                # 检查配额
                duration_estimate = await cls._estimate_audio_duration(file)
                duration_minutes = max(1, int(duration_estimate / 60)) if duration_estimate else 1
                
                quota_available = await ClientUserDao.check_quota_available(
                    db, client_user.client_user_id, duration_minutes
                )
                if not quota_available:
                    raise ServiceException(message="配额不足，请升级计划或等待下月重置")
                
                # 检查模型权限
                if request_params.model_name not in client_user.allowed_models:
                    raise ServiceException(message=f"无权使用模型: {request_params.model_name}")
                
                # 检查功能权限
                if not client_user.allowed_features.get('upload', False):
                    raise ServiceException(message="无权使用文件上传功能")
                
            elif user_id:
                user_type = 1  # 系统用户
                current_user_id = user_id
            else:
                raise ServiceException(message="用户认证失败")
            
            # 保存文件
            file_info = await cls._save_uploaded_file(file)
            
            # 生成API请求ID（用于客户端用户）
            api_request_id = str(uuid.uuid4()) if client_user else None
            
            # 创建语音记录
            speech_record = SpeechRecordDO(
                user_id=user_id,
                client_user_id=client_user.client_user_id if client_user else None,
                user_type=user_type,
                session_id=request_params.session_id,
                api_request_id=api_request_id,
                file_name=file_info['filename'],
                file_path=file_info['filepath'],
                file_size=file_info['filesize'],
                language=request_params.language,
                model_name=request_params.model_name,
                status=0,  # 处理中
                callback_url=request_params.callback_url,
                callback_status=0 if request_params.callback_url else None,
                recognition_config={
                    'use_vad': request_params.use_vad,
                    'use_punc': request_params.use_punc,
                    'use_itn': request_params.use_itn,
                    'hotwords': request_params.hotwords
                }
            )
            
            # 保存到数据库
            speech_record = await SpeechRecordDao.add_speech_record_dao(db, speech_record)
            await db.commit()
            
            try:
                # 执行语音识别
                recognition_result = await funasr_engine.recognize_file(
                    file_path=file_info['filepath'],
                    model_name=request_params.model_name,
                    language=request_params.language,
                    use_itn=request_params.use_itn,
                    use_vad=request_params.use_vad,
                    hotwords=request_params.hotwords
                )
                
                # 计算实际音频时长
                actual_duration = recognition_result.get('duration') or await cls._calculate_audio_duration(file_info['filepath'])
                
                # 更新识别结果
                await SpeechRecordDao.update_speech_status(
                    db=db,
                    speech_id=speech_record.speech_id,
                    status=1,  # 成功
                    recognize_text=recognition_result['text'],
                    confidence=recognition_result.get('confidence'),
                    recognition_details=recognition_result
                )
                
                # 更新时长
                if actual_duration:
                    speech_record.duration = actual_duration
                
                await db.commit()
                
                # 客户端用户特殊处理
                if client_user:
                    # 更新配额使用量
                    usage_minutes = max(1, int(actual_duration / 60)) if actual_duration else 1
                    await ClientUserDao.update_quota_usage(db, client_user.client_user_id, usage_minutes)
                    
                    # 记录使用统计
                    await cls.record_usage_stats(
                        db=db,
                        client_user_id=client_user.client_user_id,
                        duration=actual_duration or 0,
                        characters_count=len(recognition_result['text']),
                        model_name=request_params.model_name,
                        is_success=True,
                        response_time=0  # TODO: 记录实际响应时间
                    )
                    
                    await db.commit()
                    
                    # 发送回调通知（异步）
                    if request_params.callback_url:
                        asyncio.create_task(cls.send_callback_notification(
                            db, speech_record.speech_id, request_params.callback_url, recognition_result
                        ))
                
                return SpeechRecognizeResponse(
                    speech_id=speech_record.speech_id,
                    api_request_id=api_request_id,
                    text=recognition_result['text'],
                    confidence=recognition_result.get('confidence'),
                    duration=actual_duration,
                    language=recognition_result.get('language', request_params.language),
                    model_name=recognition_result.get('model_name', request_params.model_name),
                    status=1,
                    user_type=user_type,
                    callback_status=0 if request_params.callback_url else None,
                    create_time=speech_record.create_time
                )
                
            except Exception as e:
                # 更新错误状态
                await SpeechRecordDao.update_speech_status(
                    db=db,
                    speech_id=speech_record.speech_id,
                    status=2,  # 失败
                    error_message=str(e)
                )
                
                # 客户端用户记录失败统计
                if client_user:
                    await cls.record_usage_stats(
                        db=db,
                        client_user_id=client_user.client_user_id,
                        duration=0,
                        characters_count=0,
                        model_name=request_params.model_name,
                        is_success=False,
                        response_time=0
                    )
                
                await db.commit()
                raise e
                
        except Exception as e:
            logger.error(f"语音识别失败: {str(e)}")
            raise ServiceException(message=f"语音识别失败: {str(e)}")
    
    @classmethod
    async def get_speech_records(cls, 
                               db: AsyncSession,
                               query_params: SpeechQueryModel,
                               user_id: int = None,
                               client_user_id: int = None) -> Dict[str, Any]:
        """
        获取语音识别记录列表
        
        :param db: 数据库会话
        :param query_params: 查询参数
        :param user_id: 系统用户ID（可选）
        :param client_user_id: 客户端用户ID（可选）
        :return: 分页结果
        """
        try:
            # 根据用户类型设置查询条件
            if user_id and not query_params.user_id:
                query_params.user_id = user_id
                query_params.user_type = 1
            elif client_user_id and not query_params.client_user_id:
                query_params.client_user_id = client_user_id
                query_params.user_type = 2
            
            # 获取记录列表
            records = await SpeechRecordDao.get_speech_record_list(db, query_params)
            
            # 获取总数
            total = await SpeechRecordDao.count_speech_records(db, query_params)
            
            # 转换为响应格式
            record_list = []
            for record in records:
                record_dict = CamelCaseUtil.snake_to_camel(record.__dict__)
                record_dict.pop('_sa_instance_state', None)
                record_list.append(record_dict)
            
            return {
                'rows': record_list,
                'total': total,
                'page_num': query_params.page_num,
                'page_size': query_params.page_size
            }
            
        except Exception as e:
            logger.error(f"获取语音记录失败: {str(e)}")
            raise ServiceException(message=f"获取语音记录失败: {str(e)}")
    
    @classmethod
    async def authenticate_client_user(cls, db: AsyncSession, api_key: str, client_ip: str = None) -> Optional[SpeechClientUserDO]:
        """
        客户端用户认证
        
        :param db: 数据库会话
        :param api_key: API密钥
        :param client_ip: 客户端IP（用于白名单验证）
        :return: 客户端用户对象或None
        """
        try:
            # 根据API密钥获取用户
            client_user = await ClientUserDao.get_client_user_by_api_key(db, api_key)
            
            if not client_user:
                return None
            
            # 检查IP白名单
            if client_user.ip_whitelist and client_ip:
                if client_ip not in client_user.ip_whitelist:
                    logger.warning(f"IP {client_ip} 不在白名单中，用户ID: {client_user.client_user_id}")
                    return None
            
            # 更新最后登录信息
            if client_ip:
                await ClientUserDao.update_last_login(db, client_user.client_user_id, client_ip)
                await db.commit()
            
            return client_user
            
        except Exception as e:
            logger.error(f"客户端用户认证失败: {str(e)}")
            return None
    
    @classmethod
    async def check_quota_limit(cls, db: AsyncSession, client_user_id: int, required_minutes: int) -> bool:
        """
        检查配额限制
        
        :param db: 数据库会话
        :param client_user_id: 客户端用户ID
        :param required_minutes: 需要的分钟数
        :return: 是否有足够配额
        """
        return await ClientUserDao.check_quota_available(db, client_user_id, required_minutes)
    
    @classmethod
    async def record_usage_stats(cls, db: AsyncSession, client_user_id: int, 
                                duration: float, characters_count: int, 
                                model_name: str, is_success: bool, response_time: float):
        """
        记录使用统计
        
        :param db: 数据库会话
        :param client_user_id: 客户端用户ID
        :param duration: 音频时长
        :param characters_count: 字符数量
        :param model_name: 模型名称
        :param is_success: 是否成功
        :param response_time: 响应时间
        """
        try:
            await ClientUsageDao.update_daily_stats(
                db=db,
                client_user_id=client_user_id,
                usage_date=date.today(),
                requests_count=1,
                duration=duration,
                characters_count=characters_count,
                model_name=model_name,
                is_success=is_success,
                response_time=response_time
            )
        except Exception as e:
            logger.error(f"记录使用统计失败: {str(e)}")
    
    @classmethod
    async def send_callback_notification(cls, db: AsyncSession, speech_id: int, 
                                       callback_url: str, result_data: Dict[str, Any]):
        """
        发送回调通知
        
        :param db: 数据库会话
        :param speech_id: 语音记录ID
        :param callback_url: 回调URL
        :param result_data: 结果数据
        """
        try:
            # 准备回调数据
            callback_data = {
                'speech_id': speech_id,
                'status': 'completed',
                'text': result_data.get('text', ''),
                'confidence': result_data.get('confidence'),
                'duration': result_data.get('duration'),
                'language': result_data.get('language'),
                'model_name': result_data.get('model_name'),
                'timestamp': datetime.now().isoformat()
            }
            
            # 发送HTTP回调
            async with aiohttp.ClientSession() as session:
                async with session.post(
                    callback_url,
                    json=callback_data,
                    timeout=aiohttp.ClientTimeout(total=30)
                ) as response:
                    if response.status == 200:
                        # 更新回调状态为成功
                        await SpeechRecordDao.update_callback_status(db, speech_id, 1)
                        logger.info(f"回调成功发送，语音ID: {speech_id}")
                    else:
                        # 更新回调状态为失败
                        await SpeechRecordDao.update_callback_status(db, speech_id, 2)
                        logger.error(f"回调发送失败，状态码: {response.status}, 语音ID: {speech_id}")
            
            await db.commit()
            
        except Exception as e:
            # 更新回调状态为失败
            try:
                await SpeechRecordDao.update_callback_status(db, speech_id, 2)
                await db.commit()
            except:
                pass
            
            logger.error(f"发送回调通知失败: {str(e)}, 语音ID: {speech_id}")
    
    @classmethod
    async def get_speech_record_detail(cls, 
                                     db: AsyncSession,
                                     speech_id: int,
                                     user_id: int = None,
                                     client_user_id: int = None) -> Dict[str, Any]:
        """
        获取语音记录详情
        
        :param db: 数据库会话
        :param speech_id: 语音记录ID
        :param user_id: 系统用户ID（可选，用于权限控制）
        :param client_user_id: 客户端用户ID（可选，用于权限控制）
        :return: 记录详情
        """
        try:
            record = await SpeechRecordDao.get_speech_record_detail_by_id(db, speech_id)
            
            if not record:
                raise ServiceException(message="语音记录不存在")
            
            # 权限检查
            if user_id and record.user_id != user_id:
                raise ServiceException(message="无权访问该记录")
            
            if client_user_id and record.client_user_id != client_user_id:
                raise ServiceException(message="无权访问该记录")
            
            record_dict = CamelCaseUtil.snake_to_camel(record.__dict__)
            record_dict.pop('_sa_instance_state', None)
            
            return record_dict
            
        except Exception as e:
            logger.error(f"获取语音记录详情失败: {str(e)}")
            raise ServiceException(message=f"获取语音记录详情失败: {str(e)}")
    
    @classmethod
    async def delete_speech_records(cls, 
                                  db: AsyncSession,
                                  delete_params: SpeechDeleteModel,
                                  user_id: int = None,
                                  client_user_id: int = None) -> bool:
        """
        删除语音识别记录
        
        :param db: 数据库会话
        :param delete_params: 删除参数
        :param user_id: 系统用户ID（可选，用于权限控制）
        :param client_user_id: 客户端用户ID（可选，用于权限控制）
        :return: 删除是否成功
        """
        try:
            # 权限检查：确保用户只能删除自己的记录
            if user_id or client_user_id:
                for speech_id in delete_params.speech_ids:
                    record = await SpeechRecordDao.get_speech_record_detail_by_id(db, speech_id)
                    if record:
                        if user_id and record.user_id != user_id:
                            raise ServiceException(message=f"无权删除记录 {speech_id}")
                        if client_user_id and record.client_user_id != client_user_id:
                            raise ServiceException(message=f"无权删除记录 {speech_id}")
            
            # 执行删除
            result = await SpeechRecordDao.delete_speech_record_dao(db, delete_params.speech_ids)
            await db.commit()
            
            if result:
                logger.info(f"成功删除语音记录: {delete_params.speech_ids}")
            
            return result
            
        except Exception as e:
            logger.error(f"删除语音记录失败: {str(e)}")
            raise ServiceException(message=f"删除语音记录失败: {str(e)}")
    
    @classmethod
    async def process_realtime_speech(cls,
                                    audio_chunk: bytes,
                                    session_id: str,
                                    model_name: str = 'sensevoice-small-streaming',
                                    is_final: bool = False,
                                    user_id: int = None,
                                    client_user_id: int = None) -> RealtimeSpeechMessage:
        """
        处理实时语音识别
        
        :param audio_chunk: 音频数据块
        :param session_id: 会话ID
        :param model_name: 模型名称
        :param is_final: 是否为最终块
        :param user_id: 系统用户ID（可选）
        :param client_user_id: 客户端用户ID（可选）
        :return: 实时识别消息
        """
        try:
            # 调用识别引擎处理
            result = await funasr_engine.process_realtime_chunk(
                audio_chunk=audio_chunk,
                session_id=session_id,
                model_name=model_name,
                is_final=is_final
            )
            
            return RealtimeSpeechMessage(
                type='result',
                session_id=session_id,
                text=result.get('text', ''),
                is_final=is_final,
                confidence=result.get('confidence'),
                timestamp=datetime.now()
            )
            
        except Exception as e:
            logger.error(f"实时语音识别失败: {str(e)}")
            return RealtimeSpeechMessage(
                type='error',
                session_id=session_id,
                data=str(e),
                timestamp=datetime.now()
            )
    
    @classmethod
    async def clear_session(cls, session_id: str):
        """
        清理会话资源
        
        :param session_id: 会话ID
        """
        try:
            await funasr_engine.clear_session(session_id)
            logger.info(f"会话资源清理完成: {session_id}")
        except Exception as e:
            logger.error(f"清理会话资源失败: {str(e)}")
    
    @classmethod
    async def get_supported_models(cls) -> List[str]:
        """
        获取支持的模型列表
        
        :return: 模型列表
        """
        return await funasr_engine.get_available_models()
    
    @classmethod
    async def _estimate_audio_duration(cls, file: UploadFile) -> Optional[float]:
        """
        估算音频时长（基于文件大小的粗略估算）
        
        :param file: 音频文件
        :return: 估算时长（秒）
        """
        try:
            # 简单的估算：假设平均比特率为128kbps
            file_size = file.size or 0
            estimated_duration = file_size / (128 * 1024 / 8)  # 文件大小 / (比特率 / 8)
            return max(1.0, estimated_duration)  # 至少1秒
        except:
            return 1.0  # 默认1秒
    
    @classmethod
    async def _validate_audio_file(cls, file: UploadFile):
        """
        验证音频文件
        
        :param file: 上传的文件
        """
        if not file.filename:
            raise ServiceException(message="文件名不能为空")
        
        # 检查文件扩展名
        file_ext = Path(file.filename).suffix.lower()
        if file_ext not in cls.SUPPORTED_AUDIO_FORMATS:
            raise ServiceException(
                message=f"不支持的音频格式: {file_ext}，支持的格式: {', '.join(cls.SUPPORTED_AUDIO_FORMATS)}"
            )
        
        # 检查文件大小
        if file.size and file.size > cls.MAX_FILE_SIZE:
            raise ServiceException(message=f"文件大小超过限制: {cls.MAX_FILE_SIZE / (1024*1024)}MB")
    
    @classmethod
    async def _save_uploaded_file(cls, file: UploadFile) -> Dict[str, Any]:
        """
        保存上传的文件
        
        :param file: 上传的文件
        :return: 文件信息
        """
        # 生成唯一文件名
        file_ext = Path(file.filename).suffix.lower()
        unique_filename = f"{uuid.uuid4().hex}{file_ext}"
        file_path = cls.UPLOAD_DIR / unique_filename
        
        # 保存文件
        async with aiofiles.open(file_path, 'wb') as f:
            content = await file.read()
            await f.write(content)
        
        return {
            'filename': file.filename,
            'unique_filename': unique_filename,
            'filepath': str(file_path),
            'filesize': len(content)
        }
    
    @classmethod
    async def _calculate_audio_duration(cls, file_path: str) -> Optional[float]:
        """
        计算音频文件实际时长
        
        :param file_path: 文件路径
        :return: 音频时长（秒）
        """
        try:
            # TODO: 实现音频时长计算逻辑
            # 可以使用 ffprobe 或其他音频处理库
            # 这里先返回一个简单的估算值
            file_size = os.path.getsize(file_path)
            estimated_duration = file_size / (128 * 1024 / 8)  # 估算
            return max(1.0, estimated_duration)
        except Exception as e:
            logger.warning(f"计算音频时长失败: {str(e)}")
            return None 